useObservable
Scripting 提供一套响应式状态系统,由 Observable<T> 与 useObservable<T> 组成,用于驱动组件渲染、与动画系统协同工作,并与 SwiftUI 的双向绑定能力保持一致(例如 List(selection:)、NavigationStack(path:) 等未来扩展接口)。
1. Observable<T>
Observable<T> 是一个可观察的数据容器,当 .value 更新时,会触发依赖该值的 UI 自动重新渲染。
1.1 类定义
1class Observable<T> {
2 constructor(initialValue: T);
3 value: T;
4 setValue(value: T): void;
5 subscribe(callback: (value: T, oldValue: T) => void): void;
6 unsubscribe(callback: (value: T, oldValue: T) => void): void;
7 dispose(): void;
8}
1.2 属性与方法说明
value
存储当前值。读取 .value 不会产生副作用。
setValue(newValue)
更新值,并触发 UI 重绘:
1observable.setValue(newValue);
支持任何类型 T(包括对象、数组、字面量、类实例等)。
subscribe / unsubscribe
用于在组件体系外手动监听值变化。
dispose
释放监听器和内部资源。
一般无需手动调用,仅在高级场景使用。
2. useObservable<T>
useObservable<T> 是在组件内部创建本地状态的 Hook。
返回值为 Observable<T>,用于驱动 UI 更新。
2.1 函数签名
1declare function useObservable<T>(): Observable<T | undefined>;
2declare function useObservable<T>(value: T): Observable<T>;
3declare function useObservable<T>(initializer: () => T): Observable<T>;
2.2 初始化方式
1. 无初始值(value 为 undefined)
1const data = useObservable<string>();
2. 直接提供初始值
1const count = useObservable(0);
3. 惰性初始化(初次渲染时执行)
1const user = useObservable(() => createDefaultUser());
3. 在 UI 中使用 Observable
在组件中,只需读取 .value:
1<Text>{name.value}</Text>
当 .setValue 被调用,组件会自动重新渲染:
1<Button title="Tap" action={() => name.setValue("Updated")} />
无需手动触发更新,行为与 React useState 类似,但带来更 SwiftUI 式的数据绑定体验。
4. 与动画协同工作
Observable 是动画触发源。
支持以下场景:
4.1 显式动画:withAnimation
1withAnimation(() => {
2 size.setValue(size.value + 20);
3});
任何依赖 size.value 的视图都会执行动画。
4.2 隐式动画:animation 修饰符
视图可通过 animation 属性监听某个值的变化并执行动画。
正确写法:
1animation={{
2 animation: Animation.spring({ duration: 0.3 }),
3 value: size.value
4}}
示例:
1<Rectangle
2 frame={{
3 width: size.value,
4 height: size.value,
5 }}
6 animation={{
7 animation: Animation.easeIn(0.25),
8 value: size.value,
9 }}
10/>
5. 与 SwiftUI Binding 风格的 API 对接(扩展能力)
Observable 将作为未来 Scripting 的标准双向绑定机制,用于支持 SwiftUI 风格的 API,例如:
5.1 List(selection:)
1const selection = useObservable<string | undefined>(undefined)
2
3<List selection={selection}>
4 ...
5</List>
5.2 NavigationStack(path:)
1const path = useObservable<string[]>([])
2
3<NavigationStack path={path}>
4 ...
5</NavigationStack>
这类 API 使用方式与 SwiftUI 一致,开发者无需学习额外的绑定机制。
6. ForEach:推荐使用 Observable 数据源
为了获得更接近 SwiftUI 的体验,推荐使用:
1<ForEach data={observableArray} builder={(item, index) => <Text>{item.name}</Text>} />
其中:
1T extends { id: string }
为什么推荐这种写法:
示例:
1const items = useObservable([
2 { id: "1", name: "Apple" },
3 { id: "2", name: "Banana" }
4])
5
6<ForEach
7 data={items}
8 editActions="all"
9 builder={(item) => <Text>{item.name}</Text>}
10/>
7. 综合示例
1export function Demo() {
2 const visible = useObservable(true);
3 const size = useObservable(100);
4
5 return (
6 <VStack spacing={20}>
7 {visible.value && (
8 <Rectangle
9 frame={{
10 width: size.value,
11 height: size.value,
12 }}
13 background="blue"
14 animation={{
15 animation: Animation.spring({ duration: 0.4, bounce: 0.3 }),
16 value: size.value,
17 }}
18 transition={Transition.opacity()}
19 />
20 )}
21
22 <Button
23 title="Toggle Visible"
24 action={() => {
25 withAnimation(() => {
26 visible.setValue(!visible.value);
27 });
28 }}
29 />
30
31 <Button
32 title="Resize"
33 action={() => {
34 withAnimation(Animation.easeOut(0.25), () => {
35 size.setValue(size.value === 100 ? 160 : 100);
36 });
37 }}
38 />
39 </VStack>
40 );
41}
8. 总结
Observable<T> 是 Scripting 中的核心响应式数据结构
useObservable 在组件内创建状态,支持任意类型 T
- 与 UI 自动联动,无需额外刷新逻辑
- 为动画系统提供依赖值,用于属性动画与显式动画
- 为未来的 SwiftUI 风格 API 提供双向绑定能力
- ForEach 推荐使用
data: Observable<Array<T>>,获得一致的 SwiftUI 体验